/**
Strands - JavaScript Cooperative Threading and Coroutine support
Copyright (C) 2007 Xucia Incorporation
Author - Kris Zyp - kriszyp@xucia.com
 /* ***** BEGIN LICENSE BLOCK *****
  * Version: MPL 1.1/GPL 2.0/LGPL 2.1
  *
  * The contents of this file are subject to the Mozilla Public License Version
  * 1.1 (the "License"); you may not use this file except in compliance with
  * the License. You may obtain a copy of the License at
  * http://www.mozilla.org/MPL/
  *
  * Software distributed under the License is distributed on an "AS IS" basis,
  * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
  * for the specific language governing rights and limitations under the
  * License.
  * ***** END LICENSE BLOCK ***** */
function temp() {
		var standardPush = Array.prototype.push;
	try {
	var debugMode =document.location.search.indexOf("debug=true") > -1;
	}
	catch(e) { debugMode=true}
	var push = function(obj,value) {
		return standardPush.call(obj,value); // preserve default push behavoir
	}
	var suspendedFrame = null;
	var currentThread = {};
	var Suspend = {	// returned value when a coroutine is suspended, and state of the top frame when suspended
		toString: function() { return "SUSPENDED" }
	}
	/**
	 * This is wrap the given function in a try catch when debug mode is off. If an error is thrown, the error message will be displayed with an alert
	 */		
	var tryCatch = function(func) {
		return strands.errorWrapper(func)();
	}
	var specificNotify = {};
	Future = function(func,args,thisObj,callback) {
		this.topFrames = [];
		var self = this;
		this.fulfill = function(retValue) {
			if (retValue == specificNotify) {
				var targetFrames = retValue.targetFrames;
				retValue = retValue.retValue;
			}
			else
				self.value = retValue;
			var frame;
			while (frame = (targetFrames || self.topFrames).shift()) { // iterate through all the resulting threads/frames
				// This is the beginning of a resume
				currentThread = {};
		//		checkRestState();
//				if (!frame._r) 
	//				throw new Error("Future called without being in a result state");
				frame.retValue = retValue;
				if (frame.NRY)
					frame.thread = currentThread;
				while (frame.parentFrame) {
					frame = frame.parentFrame; // now the bottom frame
					frame.thread = currentThread;
				}
				if (frame.args) {
					suspendedFrame = frame; // the .parents indicates it was a NotReadyYet function, so there is no suspended frames above it
					tryCatch(function() {
						frame.args.callee.call(frame.frameThis); // now resume	
					});
				}
				else {
					//A thread was resumed with no call stack
					suspendedFrame = null;
				}
			}
		}
		if (func) {
			if (callback)
				this.addListener(callback);
			(function() {
				var f = _frm(arguments);
				
				var value = func.apply(thisObj || window,args||[]);
				if (value===_S) return f.sus();
				self.fulfill(value);
			})();			
		}
	}
	Future.prototype = {
		addListener : function(func) {
			push(this.topFrames,func);
		},
		interrupt : function () {
			this.fulfill(strands.INTERRUPTED);
		},
		isDone : function() {
			return this.hasOwnProperty('value');
		},
		result : function(timeout) {
			if (this.hasOwnProperty('value') || (suspendedFrame && suspendedFrame.retValue)) { // The future has been called, we can resume
				var value = (suspendedFrame ? suspendedFrame.retValue : 0) || this.value;
				suspendedFrame = null; // make sure we don't get any callers picking this up, we may not need this
				if (value == strands.TIMEOUT || value== strands.INTERRUPTED)
					throw value;
				return value;// the future has already been fulfilled so we can just return the result
			}
			var topFrame = {}
			push(this.topFrames,topFrame);
			var self = this;
			topFrame.args = [];
			topFrame.args.callee=function() {return self.result()};
			suspendedFrame = topFrame;
			if (timeout) {
				setTimeout(function() {
					self.fulfill(specificNotify = {retValue:strands.TIMEOUT,targetFrames:[topFrame]});
				},timeout);
			}
			return Suspend;
		}
	}

	var CallFrame = function() {
	}
	var Construct = function() {};
	var defaultScope = CallFrame.prototype = {
		sus : function() { // this handles exceptions as well as suspensions
				for (var i = 0; i < arguments.length-2;i++) // record all the vars and params 
					this[i] = arguments[i];
				this.cp = arguments[arguments.length-2]; // record the code pointer
				this.frameThis = arguments[arguments.length-1]; // record the code pointer
				if (suspendedFrame == this)
					same;
				if (!suspendedFrame)
					NoSuspendedFrame; // This can be caused by returning Suspend without actually being in a suspension, or if _S ends up in a variable
				suspendedFrame.parentFrame = this;
				
				this.childFrame = suspendedFrame; // it needs to be reexecuted
				suspendedFrame = this;
				if (this.thread == currentThread) // Assertion
					SuspensionFrameShouldNotBeCurrentThread;
				return Suspend;
			},
		exc : function(exception) {
			if (this.ecp == null)
				throw exception;
			this.thr = true;
			this.ex  = exception;
			return this.ecp;
		},
		clsr : function(func) {
			if (this.scp)
				this.inner = func;
			return this.inner;
		},
		scp : 2,
		keys : function(obj) {
			var keys = [];
			for(var n in obj) 
				push(keys,n);
			return keys;
		},
		_new : function(value,args) { // create a new instance of an object or constructor
			if (value === Suspend)
				return value;
			var frame = _frm(arguments);				
			if (!frame._cp) {
				frame._cp=1;
				frame.Const= value;
				Construct.prototype = value.prototype;
				if (value === String || value === Number) // these classes must not have a target this
					return args.length?new value(args[0]):new value;
				if (value !== Date) { // date does not have to directly instantiated, but it does need an undefined scope, it also needs to be able to handle variable arguements
					frame.newThis = new Construct();
				}
			}
			if ((value = frame.Const.apply(frame.newThis,args?args:[])) == Suspend) return frame.sus();
			if (value instanceof Object) return value; // if the new returns a different value than "this"			
			return frame.newThis;
		}
	}
	/**
	 * Suspend execution for the given amount time
	 * @param time	milliseconds to pause
	 */
	sleep = function(time) {
		var frame = _frm(arguments);
		if (!frame._cp) { // if it is zero
			frame._cp = 1;
			setTimeout((frame.future = new Future).fulfill,time);
			frame.future.result();
			return frame.sus();
		}
		frame.future.result(); // this is the result operation to resume
	}

	strands = { 
		defaultScope : defaultScope,
		loadScope : function(){},
		/**
		 * This function that will be called to return a function that should execute the provided function in order to initialize the stack 
		 * Any time a new stack is created, the returned function will be used. This provides a mechanism to wrap all processing
		 * within a try catch.
		 */
		errorWrapper : function(func) {
			var newFunc = function() {
				if (debugMode)
					return func.apply(this,arguments);			
				try {
					return func.apply(this,arguments);			
				}
				catch (e) {
					alert(e.message || e);
				}
			}
			newFunc.noTryCatch = func;
			return newFunc;
		},
		TIMEOUT : {toString:function(){return "Thread timeout"}},
		INTERRUPTED : {toString:function(){return "Thread interrupted"}},
		sleep : sleep,
		/**
		 * This is a constant that is returned from functions to indicate that the code execution is suspending
		 */
		Suspension : _S = Suspend,

	/**
 * Makes an XHR (Ajax) request using the given url, method, and post data (for POST requests) and 
	returns contents that came from the server.

 * @param {Object} url
 * @param {Object} method
 * @param {Object} postData
 */
	request : function(url, method, postData) {
		var frame = _frm(arguments);
		if (!frame._cp) {
			var getXMLHttpRequest = function () {
				if (parent.XMLHttpRequest)
			        return new parent.XMLHttpRequest();
				else if (window.ActiveXObject)
			        return new ActiveXObject("Microsoft.XMLHTTP");
			}
		 	var xhr = getXMLHttpRequest();
			frame.future = new Future();
			var ajaxDataReader = function () {
				if (xhr.readyState == 4) {
			        // only if "OK"
			        var loaded;
			        try {
			        	var status = xhr.status;
			        	loaded = xhr.responseText.length > 0;//firefox can throw an exception right here
			        } catch(e) {}
			        if (loaded) 
	    				frame.future.fulfill(xhr.responseText);				
					else
						frame.future.interrupt();
	        		xhr = null; // This is to correct for IE memory leak
				}
			}
			frame._cp = 1;
		    xhr.open(method || "POST", url, true); 
			xhr.onreadystatechange = ajaxDataReader;
		    xhr.send(postData);
		}
			
		var result = frame.future.result();
		if (result == _S) frame.sus();
		return result;
	},
	compilerUrl : 'js/compiler.js',
	js17 : (function() { try {return Iterator}catch(e){}})(), // something better would be good?
		/**
		 * Loads the given script. It will compile the script if necessary for coroutine support. 
		 * You can set strands.precompiled = true if the scripts have
		 * been precompiled on the server. It was HIGHLY recommended
		 * that you compiled the scripts on the server to reduce the overhead
		 * on the client. 
		 * @param {Object} url
		 */
	
	loadScript : function(url) {	
		var frame = _frm(arguments);
		switch(frame._cp) {
			case undefined:frame.future = new Future();
				frame.url = url;
				frame._cp = 1;
			case 1: case 2:case 3:url = frame.url;
			function load() {
				if (strands.js17) {
					if (frame._cp != 2) {
					    var scriptTag = document.createElement("script"); // must use the document that supports events, not his one
					    scriptTag.onload = frame.future.fulfill;
					    scriptTag.type= 'application/javascript;version=1.7';
						scriptTag.setAttribute("src", url);
					    document.body.appendChild(scriptTag); // This is supposed to be asynchronous, but sometimes it will get its request before continuing
				    }
					frame._cp = 2;
				    return frame.future.result();
			    }			    
				else {
					var source = strands.request(url,'GET');
					if (source == Suspend) return Suspend; //we can only do this because it is an inner function
					eval(source);
				}
			}
			var compOptions,shorten;
			if (url.match(/\.jss$/)) {
				compOptions = {};
				shorten = 1;
			}
			else if (url.match(/\.js17$/)) {
				if (strands.js17) 
					shorten = 0;
				else {
					compOptions = {source:'js17'};
					shorten = 2;
				}				
			} 
			if (strands.precompiled) 
				url = url.substring(0,url.length-shorten);
			else
			{
				if (compOptions) {
					if (!strands.compiler || frame._cp == 2) {
						frame._cp = 2; 
						var compiler;
						if ((compiler = strands.request(strands.compilerUrl,'GET'))==Suspend)
							return frame.sus();
						eval(compiler);
						frame._cp = 3;
						strands.compiler = new StrandsCompiler(compOptions);
					}
					var source;
					if ((source = strands.request(url,'GET'))==Suspend)
						return frame.sus();
					source = strands.compiler.compile(source);
					eval(source);
					return;
				}
			}
			if (load() == Suspend) return frame.sus();
		}
	}
}
var coreturn;
strand =  function() {throw new Error("can not call strand except as a function wrapper");};	/** 
	 * This creates a new Strands call frame. It becomes the scope for the function maintains variables and parameters across suspensions and resumes. It should only be used in compiled code
	 */	
	_frm = function(args) {
		if (args.caller) args.caller=0; // this fixes a big memory leak;
		if (suspendedFrame) {
			// if it is loading we start a new frame
			if (suspendedFrame.thread == currentThread && suspendedFrame.args && !suspendedFrame.NRY) {// this means we are resuming
				var frame = suspendedFrame;
				//TODO: Implement this:
				if (frame.args.callee != args.callee && frame.args.callee.toString() != args.callee.toString()) {// this means the function that is being called has changed, the function has been replaced, so we need to call the orginal one
	//				if (this!=frame.frameThis) {
						suspendedFrame = null;
						StackAlterationError("The function has changed, the means for handling this right now is not complete");
		//			}
/*					var retValue = frame.args.callee.call(frame.frameThis);
					if (retValue == _S){
						// this is the tricky part, we need to call the next function and have it be guaranteed to return a _S
					}
					else {// we need to come up with a way to ensure that we have the right rv#
						frame["rv" + frame.cp++] = retValue;  //make sure we increment the cp so we are the next part
					}
					return frame;*/
				}
				delete frame.thread;
	
				suspendedFrame = frame.childFrame; // if this is undefined it means we are at the top of the resume stack
				delete frame.childFrame; // for assertions
				if (suspendedFrame && suspendedFrame._r) {//Assertion stuff
					if (! suspendedFrame.parentFrame)
						SuspendedFrameHasNoParentFrame;
					else
						delete suspendedFrame.parentFrame;				
				}
				for (var i =0;i<frame.args.length;i++)
					args[i] = frame.args[i];
				frame.scp=0; // start in the suspend segment
				return frame;
			}
			else { // this case means that there is a suspendedFrame variable left over from a previous resume/suspension
				// It should only be a left over from a suspension, and it should be the bottom frame.  A resume should null out suspendedFrame
	
				suspendedFrame = null;  // a suspension took place somewhere else, so now we can get rid of this
			}
		}
		frame = new CallFrame;
		frame.scp = 1; // Why is this needed for opera to work? somewhere the prototype _cp is getting set, need to figure out why
		frame.args = args;
		return frame;
	}
	function Generator(frame){
		this.frame = frame;
	};
	Generator.prototype = {
		next : function() {
			return this.send();
		},
		send : function(value) {			
			suspendedFrame = this.frame;
			suspendedFrame.thread = currentThread;
			_receive = value;
			return this.frame.args.callee.call(this.frame.frameThis);			
		}
	} 
	_gen = function(frame) {
		return new Generator(frame);
	}
	returns = function(value) {
		coreturn = value;
		throw StopIteration;
	}
}
temp();

function NuderRequest(form, baseurl) {
	
	this.submit = function(action, k) {
		if(form.method != "POST")
			form.method = "POST";

		if(form.enctype != "multipart/form-data")
			form.enctype = "multipart/form-data";

		if(baseurl)
			form.action = "http://"+baseurl + action;
		else
			form.action = action;

		var id = TITTNuder.generateID("nuderrequest");

		var d = document.createElement("div");
		d.style.display = "none";
		d.innerHTML = "<iframe name='"+id+"' id='"+id+"'></iframe>";

		document.body.appendChild(d);
			
		var i = document.getElementById(id);
		var done = false;
			
		function clean() {
			if(done) return;
			done = true;
			i.src = "foo";
			document.body.removeChild(d);
		}
	
		//odds of contentWindow being cross-browser ---> 0
			
		var ping = TITTNuder.generateID("ping");
		i.contentDocument.body.innerHTML = ping;
	
		i.onload = function() {
			try {
				var c = "" + i.contentDocument.body.innerHTML;
				clean();
				k(c);
			}
			catch(err) {
				clean();
				k(null);
			}
			clearInterval(id);
		}
			
		var tid = setInterval(function() {
			try {
				if (i.contentDocument.body.innerHTML != ping) {
					clearInterval(tid);
				}
			}
			catch(err) {
				clearInterval(tid);
				clean();
				k(null);
			}
		}, 300);

		form.target = id;
		form.submit();
	}
	
}

function NuderResponse(body) {
	this.getBody = function() {
		return body;
	}
}

function NuderInvoker(tokens) {
	
	var tinv = new TITTInvoker(tokens, "nuder");
	var key = KeyFactory.createKey(tokens);
	var ef = IOFactory.createEncodingFacade();

	this.dynCall = function(methodName, args, callback) {
		tinv.dynCall(methodName, args, callback);
	}
	
	this.call = function(methodName, args, callback){
		var nreq = null;	

		if(isNuderRequest(args[args.length -1])) {
			var targs = [];
			for(var i = 0; i < args.length-1; i++)
				targs[i] = args[i];
			nreq = args[args.length-1].getObject();
			args = targs;
		}
		var m = new MethodHeadInit(methodName, args, ef, new RequestHeadInit(TITTValues.GET, key.asKeyString(), HeadFactory.EMPTY)); 

		if(nreq) {
			nreq.submit(m.getPath(), function(val) {
				doCallback(new NuderResponse(val), callback);	
			});
		}
		else {
			var ts = IOFactory.createTITTSocket();
			ts.getHeadWriter().writeHead(m);
			ts.getOutputStream().flush(function() {
				ts.getHeadReader().readHead();
				doCallback(new NuderResponse(ts.getInputStream().readFully()), callback);
			});

		}
	}
	
	function doCallback(obj, callback) {
//		out("in cb")
		callback(new TypedObject(new TypeFromObject(obj), obj));
	}
	
	function isNuderRequest(t) {
		var s = "function NuderRequest("
		
		var m = "" + t.getObject().constructor;
		return m.substring(0, s.length) == s;
	}
}
function Head(stat, headers) {

	var map = new Array();
	
	for(var id in headers)
		map[headers[id].getName()] = headers[id];
				
	this.getHeader = function(nam) {
		return map[nam];
	}
	
	this.getAllHeaders = function() {
		return headers;
	}

	this.getStatusLine = function() {
		return stat;
	}
	
}

function Header(nam,value) {
	
	this.getName = function() {
		return nam;
	}
	
	this.getValue = function() {
		return value;
	}
	
	this.equals = function(h) {
		if(h.getName)
			return this.getName() == h.getName();
		else
			return false;
	}
}

function ContentHead(h) {

	AbstractHead.call(this, h);
				
	this.getContentLength = function() {
		return parseInt(this.getHeader("Content-Length").getValue());
	}

	this.getContentType = function() {
		return this.getHeader("Content-Type").getValue();
	}

	this.containsContentLength = function() {
		return this.getHeader("Content-Length") != null;
	}
}

function ContentHeadInit(len, contentType, h) {
	ContentHead.call(this, HeadFactory.createHead2(h, [new Header("Content-Length", "" + len), new Header("Content-Type", contentType)]));	
}
function FormHead(req) {

	AbstractRequest.call(this, req);
	
	var boundary = this.getHeader("Content-Type").getValue().split("boundary=")[1];
		
	this.getBounds = function() {
		return [TITTValues.MULTIPART_SEPERATOR + boundary, TITTValues.MULTIPART_SEPERATOR + boundary + TITTValues.MULTIPART_SEPERATOR];
	}
	
	this.getBoundary = function() {
		return boundary;
	}
	
	this.getContentLength = function() {
		return parseInt(this.getHeader("Content-Length").getValue());
	}

	this.containsContentLength = function() {
		return this.getHeader("Content-Length") != null;
	}

}

function FormHeadInit(contentLength, boundary, h) {
	FormHead.call(this, new RequestHead(new ContentHeadInit(contentLength, "multipart/form-data;boundary="+boundary, h)));	
}
function MultipartHead(h) {
	AbstractHead.call(this,h);

	this.getName = function() {
		return this.getHeader("Content-Disposition").getValue().split("name=\"")[1].split("\"")[0];
	}
	
	this.getType = function() {
		return new Type(this.getName());
	}
}

function MultipartHeadInit(t) {
	MultipartHead.call(this, HeadFactory.createHead1([new Header("Content-Disposition", "form-data; name=\""+t.asString()+"\"")]));
}

function MethodHead(dec, req) {

	var methodName, path; 
	var param_values;
	
	AbstractRequest.call(this, req);
					
	setMethodName();
	setPath();
	setParams(dec);
		
	this.getNonMethodPath = function() {
		return path;
	}
	
	this.getParams = function() {
		return param_values;
	}
		
	this.getMethodName = function() {
		return methodName;
	}
	
	
	function setParams(d) {
		var index = req.getPath().indexOf("?");
		param_values = new Array();
		if(index == -1)
			return;
		
		var param_string = req.getPath().substring(index+1, req.getPath().length);

		if(param_string == "")
			return;
			
		var params = param_string.split("&");

		for(var i = 0; i < params.length; i++) {
			var spl = params[i].split("=");
			if(spl.length == 1)
				spl = [spl[0], ""];

			var t = new Type(spl[0]);
			var o = null;
			if(d.isDecodeable(t))
				o = d.decode(t, spl[1]);
			
			param_values[i] = new TypedObject(t, o);
		}
		
	}
	
	function setPath() {
		var p = req.getPath();
		if(methodName == null || methodName == undefined) {
			path = p;
			return;
		}
		path = p.substring(0, p.indexOf(methodName + "?"));
		
	}
	
	function setMethodName() {
				
		var all_method = req.getPath();
		var index = all_method.indexOf("?");

		if(index == -1) {
			methodName = null;
			return;
		}
		
		methodName =  all_method.substring(0, index);
		methodName = methodName.substring(methodName.lastIndexOf("/") + 1, methodName.length);
	}

}

function MethodHeadInitNoArgs(method_name, req) {
	MethodHeadInit.call(this,method_name,[],null,req);
}
function MethodHeadInit(method_name, params, enc, req) {
	
	var path;
		
	var method_path = req.getPath();
	if(!(method_path.charAt(method_path.length-1) == "/")) method_path += "/";
	
	path = method_path;
		
	method_path += method_name + "?";
		
	for(var id in params) {
		var p = params[id];
		
		if(enc.isEncodeable(p.getType())) {
			
			if(!(method_path.charAt(method_path.length-1) == "?")) method_path += "&";
			
			var enced = enc.encode(p.getType(), p.getObject());
			var val = "";
			if(enced.length > 0)
				val = "=" + enced;
			
			method_path += p.getType().asString() + val;
		}
	}
		
	req = new RequestHeadInit(req.getHTTPMethod(), method_path, req);

	AbstractRequest.call(this, req);
	
	this.getNonMethodPath = function() {
		return path;
	}
	
	this.getParams = function() {
		return params;
	}
		
	this.getMethodName = function() {
		return method_name;
	}
	
}
function RequestHead(h) {
	
	AbstractHead.call(this, h);
	
	this.getHTTPMethod = function() {
		var method = h.getStatusLine().split(" ")[0];
		
		if(method == "GET") return TITTValues.GET;
		if(method == "POST") return TITTValues.POST;
		if(method == "OPTIONS") return TITTValues.OPTIONS;
		
		return -1;
	}

	this.getPath = function() {
		return h.getStatusLine().split(" ")[1];
	}

}

function RequestHeadInit(method,path,h) {
	if(!(path.charAt(0) == "/")) path = "/" + path;
	RequestHead.call(this, HeadFactory.createHead3(h, TITTValues.methodAsString(method) + " " + path + " HTTP/1.1", []));
}
function ServerPrefixHead(req) {

	var PREFIX_DETECTION = "/" + KeyFactory.SERVER_TOKEN + "/";
	
	var stat, prefix;
	
	var toks = req.getStatusLine().split(" ");

	if(toks[1].substring(0,PREFIX_DETECTION.length) == PREFIX_DETECTION) {
		prefix = toks[1].substring(PREFIX_DETECTION.length, toks[1].length).split("/")[0];
		stat = toks[0] + " " + toks[1].substring((PREFIX_DETECTION + prefix).length, toks[1].length) + " " + toks[2];
	}
	else {
		stat = req.getStatusLine();
		prefix = null;
	}
	
	AbstractRequest.call(this, req);


	this.getStatusLine = function() {
		return stat;
	}

	this.getPrefix = function() {
		return prefix;
	}

	this.getPath = function() {
		return this.getStatusLine().split("\\ ")[1];
	}
}

function ServerPrefixHeadInit(prefix, req) {
	var PREFIX_DETECTION = "/" + KeyFactory.SERVER_TOKEN + "/";
	
	var pre = prefix;
	
	if(pre == null || pre == undefined) 
		pre = req.getPath();
	else
		pre = PREFIX_DETECTION + prefix + req.getPath();
	
	
	AbstractRequest.call(this, new RequestHeadInit(req.getHTTPMethod(), pre, req));
	
	this.getPrefix = function() {
		return prefix;
	}
}

function ResponseHead(h) {
	ResponseHeadInit.call(this, parseInt(h.getStatusLine().split(" ")[1]),h);
}

function ResponseHeadInit(stat, h) {
	h = HeadFactory.createHead3(h, TITTValues.getResponse(stat), []);
	AbstractHead.call(this,h);
		
	this.getStatusCode = function() {
		return stat;
	}
	
	this.getContentType = function() {
		return h.getHeader("Content-Type").getValue();
	}
	
}
function AbstractContent(h) {
	
	AbstractHead.call(this, h);
	
	this.getContentLength = h.getContentLength;
	this.containsContentLength = h.getContentLength;
	

}

function AbstractHead(h) {
	
	this.getAllHeaders = h.getAllHeaders;
	this.getHeader = h.getHeader;
	this.getStatusLine = h.getStatusLine;

}

function AbstractRequest(h) {
	AbstractHead.call(this, h);
	
	this.getHTTPMethod = h.getHTTPMethod;
	this.getPath = h.getPath;

}

function ArrayDecoder(non_array) {
	
	this.non_array = non_array;

	this.decode = function(t, b) {		
		if(t.isArray()) {

			if(b == Types.NULL_TOKEN) return null;		

			var depth = 0;
			var sub = t.subtype();
			var onemore = false;
			
			var buf = "";
			var arr = new Array();
			var index = 0;

			for(var i = 1; i < b.length - 1; i++) {
				var c = b.charAt(i);

				if(c == Types.ARRAY_START)	depth++;
				if(c == Types.ARRAY_END) 	depth--;

				if(depth == 0 && c == Types.ARRAY_SPLITTER) {
					if(buf.length > 0) {
						arr[index] = this.decode(sub, buf);
						index++;
					}

					buf = "";
					onemore = false;
				}
				else {
					onemore = true;
					buf += c;
				}
			}
			if(onemore) {
				if(buf.length > 0) {
					arr[index] = this.decode(sub, buf);
					index++;
				}
			}

			return arr;
		}		

		return this.non_array.decode(t, b);
	}

	this.isDecodeable = function(t) {
		return (t.isArray() && this.isDecodeable(t.subtype())) || this.non_array.isDecodeable(t);
	}

}
function ArrayEncoder(non_array) {
	
	this.non_array = non_array;
	
	this.encode = function(t, o) {
		if(t.isArray()) {
			if(o == null) return Types.NULL_TOKEN;
			
			var output = "";
				
			output += Types.ARRAY_START;
				
			for(var i = 0; i < o.length; i++) {
				if(i > 0) output += Types.ARRAY_SPLITTER;
				output += this.encode(t.subtype(), o[i]);
			}
				
			output += Types.ARRAY_END;
			
			return output;
		}
		return this.non_array.encode(t, o);
	}

	this.isEncodeable = function(t) {
		return (t.isArray() && this.isEncodeable(t.subtype())) || this.non_array.isEncodeable(t);
	}

}

function CompositeDecoder(encs,off) {

	var EMPTY = new function() {
		this.decode = function(t, o) {
			return null;
		}
		this.isDecodeable = function(t) {
			return false;
		}
	};
	
	this.encs = encs;
	this.pri; 
	this.sec;
	this.off = 0;
	
	if(off) {
		this.off = off;
	}

	this.pri = this.encs[this.off];

	if(this.off < this.encs.length-1)
		this.sec = new CompositeDecoder(this.encs,this.off+1);
	else
		this.sec = EMPTY;
		

	this.decode = function(t, o) {
		if(this.pri.isDecodeable(t))
			return this.pri.decode(t, o);
		else
			return this.sec.decode(t, o);
	}
	
	this.isDecodeable = function(t) {
		return this.pri.isDecodeable(t) || this.sec.isDecodeable(t);
	}	
}

function CompositeEncoder(encs,off) {

	var EMPTY = new function() {
		this.encode = function(t, o) {
			return null;
		}
		this.isEncodeable = function(t) {
			return false;
		}
	};
	
	this.encs = encs;
	this.pri;
	this.sec;
	this.off = 0;
	
	if(off) {
		this.off = off;
	}

	this.pri = this.encs[this.off];

	if(this.off < this.encs.length-1)
		this.sec = new CompositeEncoder(this.encs,this.off+1);
	else
		this.sec = EMPTY;
		

	this.encode = function(t, o) {
		if(this.pri.isEncodeable(t))
			return this.pri.encode(t, o);
		else
			return this.sec.encode(t, o);
	}
	
	this.isEncodeable = function(t) {
		return this.pri.isEncodeable(t) || this.sec.isEncodeable(t);
	}	
}

function ObjectDecoder() {
	
	this.decode = function(t,e) {
		eval("var deserialize = " + unescape(e));
		return deserialize();
	}
	
	this.isDecodeable = function(t) {
		return t.isNative();
	}
}
function ObjectEncoder() {
	
	this.encode = function(t,o) {
		var sers = new Array();
		var names = new Array();
		var i = 0;
				
		function ser(o) {
									
			if(o == undefined) 
				return "function() { return; };";
			if(o == null) 
				return "function() { return null; };";
			
			if(o.constructor == Function || typeof(o) == "function") {
				return "function() { return " + o + "; };";
			}
			if(o.constructor == Boolean || typeof(o) == "boolean") {
				return "function() { return " + o + "; };";
			}
			if(o.constructor == Number || typeof(o) == "number") { 
				return "function() { return " + o + "; };";
			}
			if(o.constructor == String || typeof(o) == "string") {
				return "function() { return \"" + o + "\"; };";
			}

			for(var h in sers) {
				if(sers[h] == o) {
					return "function() { return " + names[h] + ";};";
				}
			}
			
			names[i] = "tittSerializationID"  + i;
			sers[i++] = o;
			
			var pre = "window.";
			var nam = names[i-1];

			var out = "function() { ";

			if(o.constructor == Array || typeof(o) == "array")
				out += pre + nam + " = new Array();";
			else 
				out += pre + nam + " = new Object();";

			for(var id in o) {
				out += "var id = " + ser(id) + ";";
				out += "var val = " + ser(o[id]) + ";";
				out += nam + "[id()] = val();";
			}
			
			out += "return " + nam + "; };";
			return out;
		}
		
		var out = "function() { var obj = " + ser(o) + ";";
		out += "var inst = obj();";

		for(var nam in names) {
			out += "delete window." + names[nam] + ";";
		}

		out += "return inst; };";
		return escape(out);
		
	}
	
	this.isEncodeable = function(t) {
		return t.isNative();
	}
}
function AsStringDecoder() {
	function urldecode(str) {
		return decodeURIComponent(str);
	}
	
	this.decode = function(t, enc) {
		var str = urldecode(enc);

		if(t.equals(Types.UNKNOWN)) {
			var ret = null;
			eval("ret = " + str);
			return ret;			
		}

		if(t.equals(Types.STRING)) {
			if(str == Types.EMPTY_STRING_TOKEN) return "";
			if(str == Types.NULL_TOKEN)         return null;

			return str;
		}
		
		if(t.equalsAny([Types.INT, Types.LONG, Types.SHORT, Types.BYTE])) return parseInt(str);
		if(t.equals(Types.BOOLEAN))	                                      return str == "true";
		if(t.equalsAny([Types.DOUBLE, Types.FLOAT]))                      return parseFloat(str);
		if(t.equals(Types.CHAR))                                          return str;
		

		return str;
	}
	
	this.isDecodeable = function(t) {
		return t.equalsAny([Types.UNKNOWN, Types.STRING, Types.INT, Types.LONG, Types.SHORT, 
				Types.BOOLEAN, Types.DOUBLE, Types.FLOAT, Types.BYTE, Types.CHAR]);
	}

}

function AsStringEncoder() {

	this.encode = function(t, o) {
		
		if(t.equals(Types.STRING) && (o == null || o == undefined))
			return Types.NULL_TOKEN;

		if(t.equals(Types.STRING) && o == "")
			return Types.EMPTY_STRING_TOKEN;

		return urlencode(""+o);
	}
	
	function urlencode(str) {
		return encodeURIComponent(str);
		//escape(str).repace(/\+/g,'%2B').replace(/\*/g, '%2A').replace(/\//g, '%2F').replace(/@/g, '%40');
	}
	
	this.isEncodeable = function(t) {
		return t.equalsAny([Types.UNKNOWN, Types.STRING, Types.BOOLEAN, Types.CHAR, Types.DOUBLE, 
							Types.FLOAT, Types.INT, Types.LONG, Types.SHORT, Types.BYTE]);
	}

}

function VoidDecoder() {

	this.decode = function(t, b) {
		return null;
	}

	this.isDecodeable = function(t) {
		return t.equals(Types.VOID);
	}

}

function VoidEncoder() {

	this.encode = function(t, o) {
		return "void";
	}

	this.isEncodeable = function(t) {
		return t.equals(Types.VOID);
	}

}

function HeadReader(is) {
	
	this.readHead = function() {
		return this.readHeadWFS(true);	
	}
	
	this.readHeadWFS = function(waitForStatus) {
		var stat = null, headers = new Array(), statusRead = !waitForStatus;

		var count = 0, inp;
		
		while((inp = is.readLine()) != null && (inp.length > 0 || !statusRead)) {
			if(inp.length > 0)
				if(startsWith(inp, ["GET", "POST", "HTTP", "OPTIONS"])) {
					stat = inp;
					statusRead = true;
				}
				else {
					var i = inp.indexOf(": ");
					if(i > -1) 
						headers[count++] = new Header(inp.substring(0, i), inp.substring(i+2, inp.length));
				}
		}
		
		return HeadFactory.createHead(stat, headers);

	}
	
	this.close = function() {}

	function startsWith(inp, strs) {
		for(var id in strs)
			if(inp.substring(0, strs[id].length) == strs[id])
				return true;
		return false;
	}
}
function POSTReader(ts, denc, fh) {

	this.readValues = function() {

		var data = ts.getInputStream().read(fh.getContentLength());
		return decodeData(data);

	}

	function decodeData(data) {
		var bounds = fh.getBounds();
		
		var str_vals = data.split(bounds[0]);
			
		var ots = new Array();
		var len = str_vals.length - 2;
			
		for(var i = 0; i < len; i++) {
			var staticData = str_vals[i+1].substring(TITTValues.HTTP_NEWLINE_LENGTH, str_vals[i+1].length - TITTValues.HTTP_NEWLINE_LENGTH);
			var sis = new StaticInputStream(staticData);
							
			var hr = new HeadReader(sis);
			var t = new MultipartHead(hr.readHeadWFS(false)).getType();
			
			if(!denc.isDecodeable(t))
				ots[i] = new TypedObject(t, null);
			else {
				var val = sis.readFully();
				ots[i] = new TypedObject(t, denc.decode(t, val));
			}
		}
		return ots;	
	}	
}

function ResponseReader(s, denc, responseHead) {

	this.readValues = function() {
		var c = new ContentHead(responseHead);
		
		if(!c.containsContentLength()) throw "Content-Length must be present";
		if(responseHead.getStatusCode() != 200) throw TITTValues.getResponse(responseHead.getStatusCode());

		return decode(s.getInputStream().read(c.getContentLength()),responseHead);	
	}

	function decode(data, responseHead) {

		var ret;
		
		var t = new Type(responseHead.getContentType());
		
		if(!denc.isDecodeable(t)) 
			ret = new TypedObject(t,null);
		else
			ret = new TypedObject(t, denc.decode(t, data));
 
		
		return [ret];
		
	}
}

function StaticInputStream(staticData) {
	
	var data = staticData, HTTP_NEWLINE = TITTValues.HTTP_NEWLINE;
	
	this.read = function(len) {

		if(data == "") return null;
		
		var ret = data.substring(0, len);
		data = data.substring(len, data.length);
		
		return ret;
	}
	
	this.readLine = function() {
		
		if(data == "") return null;
		
		var i = data.indexOf(HTTP_NEWLINE);
		if(i == -1) return this.read(data.length);
		
		var l = this.read(i);
		
		this.read(HTTP_NEWLINE.length);

		return l;
	}
	
	this.readFully = function() {
		return this.read(data.length);
	}
	
}
function TITTInputStream(xhr) {
	
	var dataRead = false, sis = null, HTTP_NEWLINE = TITTValues.HTTP_NEWLINE;
	
	this.read = function(len) {
		readAllData();
		return sis.read(len);
	}
	
	this.readLine = function() {
		readAllData();
		return sis.readLine();
	}
	
	this.readFully = function() {
		readAllData();
		return sis.readFully();
	}
	
	this.close = function() {
		dataRead = false;
		sis = null;
	}
	
	function readAllData() {
		if(dataRead) return;

		var stat = "HTTP/1.1 " + xhr.status + " " + xhr.statusText;
		var head = xhr.getAllResponseHeaders();
		if(head.indexOf(HTTP_NEWLINE) == -1)
			head = head.replace(/\n/g, HTTP_NEWLINE);

		var data = stat + HTTP_NEWLINE + head + HTTP_NEWLINE + xhr.responseText;

		sis = new StaticInputStream(data);
		dataRead = true;

	}
}

function TITTOutputStream(xhr) {
	
	var bodyData = null;
		
	this.write = function(data) {
		if(bodyData == null) bodyData = "";
		bodyData += data;
	}
	
	this.writeLine = function(line) {
		this.write(line + TITTValues.HTTP_NEWLINE)
	}
	
	this.flush = function(k) {
		//var id = setTimeout(function(){out("stalled " + xhr.readyState);}, 10000);
		
		xhr.onreadystatechange = function(){
		//	clearInterval(id);
			if(xhr.readyState == 4) {
				xhr.onreadystatechange = null;
				k();
			}
		}
		xhr.send(bodyData);
		bodyData = null;
	}
	
	this.close = function() {
		bodyData = null;
	}
	
}

function TITTSocket() {

	var xhr = new XMLHttpRequest();
		
	var output = new TITTOutputStream(xhr);
	var input = new TITTInputStream(xhr);
	
	var hw = new HeadWriter(xhr, output);
	var hr = new HeadReader(input);

	this.isClosed = false;
	
	this.getHeadWriter = function() {
		return hw;
	}
	
	this.getHeadReader = function() {
		return hr;
	}
	
	this.getOutputStream = function() {
		return output;
	}
	
	this.getInputStream = function() {
		return input;
	}
	
	
	this.close = function() {
		input.close();
		output.close();
		hw.close();
		hr.close();

		this.isClosed = true;
	}
}

function HeadWriter(xhr, output) {
	
	var reqWritten = false;

	this.writeHead = function(h) {
		var hs = h.getAllHeaders();

		if(reqWritten || h.getStatusLine().substring(0,4) == "HTTP") {
			var o = output;
			o.writeLine(h.getStatusLine());

			for(var id in hs)
				o.writeLine(hs[id].getName() + ": " + hs[id].getValue());

			o.writeLine("");
		}
		else {
			var toks = h.getStatusLine().split(" ");

			xhr.open(toks[0], toks[1], true);

			for(var id in hs)
				xhr.setRequestHeader(hs[id].getName(), hs[id].getValue());

			reqWritten = true;
		}
	}
	
	this.close = function() {
		reqWritten = false;
	}
	
}

function POSTWriter(ts, enc, req) {
	
	this.writeValues = function(values, k) {
		
		var bound = HeadFactory.generateBoundary();
		var bounds = getBounds(bound);
		var data = "";
		
		for(var id in values) {
			var ot = values[id];
			
			data += bounds[0] + TITTValues.HTTP_NEWLINE;
			var h = new MultipartHeadInit(ot.getType());
			var hs = h.getAllHeaders();
			for(var id1 in hs) {
				data += hs[id1].getName() + ": " + hs[id1].getValue() + TITTValues.HTTP_NEWLINE;
			}
			data +=  TITTValues.HTTP_NEWLINE;
			
			if(!enc.isEncodeable(ot.getType())) 
				data += TITTValues.HTTP_NEWLINE;
			else
				data += enc.encode(ot.getType(), ot.getObject()) + TITTValues.HTTP_NEWLINE;
			
		}
		
		data += bounds[1] + TITTValues.HTTP_NEWLINE;
	
		ts.getHeadWriter().writeHead(new FormHeadInit(data.length, bound, req));

		ts.getOutputStream().write(data);
				
		ts.getOutputStream().flush(k);
	}	

	function getBounds(bound) {

		var a = new Array();
		a[0] = TITTValues.MULTIPART_SEPERATOR + bound;
		a[1] = TITTValues.MULTIPART_SEPERATOR + bound + TITTValues.MULTIPART_SEPERATOR;
		
		return a;
	}
}

function ResponseWriter(ts, enc, resp) {

	this.writeValues = function(values, k) {
		var data = enc.encode(values[0].getType(), values[0].getObject());
		var c = new ContentHeadInit(data.length, values[0].getType().asString(), resp);

		ts.getHeadWriter().writeHead(c);		
		ts.getOutputStream().write(data);
		ts.getOutputStream().flush(k);

	}

}

function TITTKey(tokens)  {
	
	var key = KeyFactory.SEP;
	
	for(var i = 0; i < tokens.length; i++) {
		key += tokens[i] + KeyFactory.SEP;
	}
	
	this.asKeyString = function() {
		return key;
	}
	
	this.toString = this.asKeyString;	
}

function MethodInvoker(o) {	
	this.call = function(methodName, args) {
		var str = "o." + methodName + "(";
		
		var argstr = "";
		for(var i = 0; i < args.length; i++) {
			if(i > 0) argstr += ",";
			argstr += "args["+i+"].getObject()";
		}
		
		str += argstr;
		
		str += ");";

		var ret = eval(str);
		var tret = new TypedObject(new TypeFromObject(ret), ret);
		
		return tret;
	}
}

function MethodParser() {
	var ef = IOFactory.createEncodingFacade();

	this.parse = function(head) {
		return new MethodHead(ef, new RequestHead(head));
	}

}

function ServiceObjectServer(o, inv, parse, serviceID) {

	this.getObject = function() {
		return o;
	}
	
	this.getMethodInvoker = function() {
		return inv;
	}
	
	this.getMethodParser = function() {
		return parse;
	}
	
	this.getServiceID = function() {
		return serviceID;
	}
	
}

//ORIGINAL SOURCE
/*

//function BlockingProxyGenerator(proxyGen) {
//	this.generateProxy = function(tokens) {
//		var sproxy = new Object();
//		var asproxy = TITTSyncing.sync(proxyGen.generateProxy)(tokens);
//		
//		for(var id in asproxy) {
//			if(id == "toString")
//				sproxy[id] = asproxy[id];
//			else
//				sproxy[id] = TITTSyncing.sync(asproxy[id]);
//		}
//		return sproxy;
//	}
//}

function BlockingProxyGenerator(proxyGen) {
	this.generateProxy = function(invoker) {

		var sproxy = new Object();
		var asproxy = TITTSyncing.sync(proxyGen.generateProxy)(invoker);
		
		for(var id in asproxy) {
			if(id == "toString")
				sproxy[id] = function(){ return "[Proxy object]"};
			else
				sproxy[id] = TITTSyncing.sync(asproxy[id]);
		}
		return sproxy;
	}
}

*/

function BlockingProxyGenerator(proxyGen){
this.generateProxy=function(invoker){var sproxy,id,asproxy,_cp=1;var _f=_frm(arguments);return _f.clsr(function(){_s:while(1){switch(_cp){case 1:_cp=2;case 2:if((_f.v2=_f._new(
Object))===_S){break _s;}sproxy=_f.v2;_cp=3;case 3:if((_f.v3=
TITTSyncing.sync(proxyGen.generateProxy))===_S){break _s;}_cp=4;case 4:if((_f.v4=_f.v3(invoker))===_S){break _s;}asproxy=_f.v4;
if((_f.iter4=_f.keys(asproxy))==_S)break _s;_f.ctr4=0;case 6:_cp=(_f.ctr4<_f.iter4.length)?7:8;break;case 7:id=_f.iter4[_f.ctr4];
_cp=(id=="toString")?9:10;break;case 9:
sproxy[id]=function(){return "[Proxy object]";};_cp=11;break;case 10:_cp=12;case 12:if((_f.v12=
TITTSyncing.sync(asproxy[id]))===_S){break _s;}sproxy[id]=_f.v12;_cp=11;break;case 11:case 5:_f.ctr4++;_cp=6;break;case 8:
return sproxy;return;}}return _f.sus(this)}).call(this)};}

//ORIGINAL SOURCE
/*
function Lock() {
	var future = new Future();

	this.wait = function() {
		future.result();
	}
	
	this.notify = function() {
		future.fulfill();
	}
	
};
*/

function Lock(){var future,_cp=1;var _f=_frm(arguments);return _f.clsr(function(){_s:while(1){switch(_cp){case 1:_cp=2;case 2:if((_f.v2=_f._new(
Future))===_S){break _s;}future=_f.v2;

this.wait=function(){var _f = _frm(arguments);var _cp=_f.scp;_s:while(1){switch(_cp){case 1:_cp=2;case 2:if((_f.v2=
future.result())===_S){break _s;}return;case 0:_cp=_f.cp;}}return _f.sus(_cp,this)};


this.notify=function(){var _f = _frm(arguments);var _cp=_f.scp;_s:while(1){switch(_cp){case 1:_cp=2;case 2:if((_f.v2=
future.fulfill())===_S){break _s;}return;case 0:_cp=_f.cp;}}return _f.sus(_cp,this)};return;}}return _f.sus(this)}).call(this)}

/*
window.TITTSyncing = new function() {
	this.sync = function(f) {
		
		function syncf() {
			var args = "";
			for(var i = 0; i < syncf.arguments.length; i++)
				args += "syncf.arguments["+i+"],";
			
			var fu = new Future();
			
			eval("f("+args+"fu.fulfill);");
			
			return fu.result();
			
		}
		
		return syncf;
	}
}
*/

window.TITTSyncing=new function(){
this.sync=function(f){
function syncf(){var _f = _frm(arguments);var _cp=_f.scp;_s:while(1){switch(_cp){case 1:
var args="";
for(var i=0;i<syncf.arguments.length;i++)
args+="syncf.arguments["+i+"],";_cp=2;case 2:if((_f.v2=_f._new(
Future))===_S){break _s;}var fu=_f.v2;_cp=3;case 3:if((_f.v3=
eval("f("+args+"fu.fulfill);"))===_S){break _s;}_cp=4;case 4:if((_f.v4=
fu.result())===_S){break _s;}return _f.v4;return;case 0:i=_f[0];args=_f[1];fu=_f[2];_cp=_f.cp;}}return _f.sus(i,args,fu,_cp,this)}
return syncf;};};

function TITTInvoker(tokens, serviceID) {

	var id = serviceID;
	if(id == null)
		id = "titt";
	var key = KeyFactory.createKey(tokens);
	var ef = IOFactory.createEncodingFacade();

	this.dynCall = function(methodName, args, callback) {
		var targs = new Array();

		for(var id in args) {
			targs[id] = new TypedObject(new TypeFromObject(args[id]), args[id]);
		}

		this.call(methodName, targs, function(ret) { callback(ret.getObject()); });
	}
	
	this.call = function(methodName, args, callback) {
		try {
			var ts = IOFactory.createTITTSocket();
	
			var method = TITTValues.GET;
			if(args.length > 0) method = TITTValues.POST;

			var m = new MethodHeadInitNoArgs(methodName, new RequestHeadInit(method, key.asKeyString(), HeadFactory.createHead1([new Header("Accept", id)])));

			if(args.length > 0) {
				var pw = new POSTWriter(ts, ef, m);
				pw.writeValues(args, handleResponse);
			}
			else {
				ts.getHeadWriter().writeHead(m);
				ts.getOutputStream().flush(handleResponse);
			}		
		}
		catch(err) {
			callback(null);
		}
		
		function handleResponse() {
			//TODO: async error handling here ##return null in callback?
			try {
				var responseHead = new ResponseHead(ts.getHeadReader().readHead());
				var reader = new ResponseReader(ts, ef, responseHead);
			
				callback(reader.readValues()[0]);
				delete responseHead; delete reader;
			}
			catch(err) {
				callback(null);
			}
			ts.close();
			delete ts; delete method; delete m; delete pw;
		}
		
	}
	
}

function TITTProxyGenerator() {
	
	this.generateProxy = function(invoker, k) {
		invoker.dynCall("showMeYourTITTies", [], function(iface) {
			k(generateFromInterface(invoker, iface));	
		});
	}

	function generateSkeleton(len, funcName, types, f) {
		var args = "";
		for(var i = 0; i < len; i++)
			args += "p"+i+",";
			
		var aargs = args.substring(0, Math.max(args.length-1,0));

		eval("function ret("+args+"k) {f(funcName,types,[" + aargs + "],ret.arguments.length-1,ret.arguments[ret.arguments.length-1]);}");

		return ret;
	}
	
	function invoke(args, func) {
		var argstr = "";
		
		for(var i = 0; i < args.length; i++)
			if(argstr == "")
				argstr += "args["+i+"]";
			else
				argstr += ",args["+i+"]";
			
		return eval("func(" + argstr + ");");
	}
	
	function findOverload(cand, oldcand) {
		function f() {
			if(f.arguments.length == cand.length)
				return invoke(f.arguments, cand);
			return invoke(f.arguments, oldcand);
		}
		return f;
	}
	
	function createHandler(k) {
		return function(retval){
			k(retval.getObject());
		}
	}

	function generateFromInterface(invoker, iface) {
		var ret = new Object();
		var inc = 0;
		
		
		for(var i = 1; i < iface.length; i++) {
						
			var types = [], funcName = iface[i][1];
			
			for(var h = 2; h < iface[i].length; h++)
				types[types.length] = new Type(iface[i][h]);			
			
			var skel = generateSkeleton(types.length, funcName, types, createInvokeHandler(invoker));

			if(ret[funcName])
				ret[funcName] = findOverload(skel, ret[funcName]);
			else
				ret[funcName] = skel;			
		}
		
		return ret;
	}
	
	function createInvokeHandler(invoker) {
		return function(funcName, types, args, arglen, k) {

				var typedArgs = [];
				
				for(var j = arglen; j < args.length; j++)
					args[j] = null;

				for(var j = 0; j < types.length; j++) {
					if(types[j].equals(Types.UNKNOWN))
						typedArgs[typedArgs.length] = new TypedObject(new TypeFromObject(args[j]),args[j]);
					else
						typedArgs[typedArgs.length] = new TypedObject(types[j], args[j]);
				}
				invoker.call(funcName, typedArgs, createHandler(k));

			}
	}
	
}


/*
function TITTProxyGenerator() {
		
	this.generateProxy = function(tokens, k) {
		var inv = new TITTInvoker(tokens);
		
		inv.dynCall("showMeYourTITTies", [], function(iface) {
			k(generateFromInterface(inv, iface));
		});
	}
	
	function generateFromInterface(d, iface) {
			var retObj = new Object();
		
			for(var i = 1; i < iface.length; i++) {

				var ret = new Type(iface[i][0]);
				var nam = iface[i][1];
				var args = new Array();
				var argstr = "", targstr = "";
				
				for(var h = 2; h < iface[i].length; h++)
					args[h-2] = new Type(iface[i][h]);
				
				for(var h = 0; h < args.length; h++) {
					if(h > 0) targstr += ",";
					argstr += "arg" + h + ",";
					
					var t = "new Type(\"" + args[h].asString() + "\")";
					if(args[h].equals(Types.UNKNOWN))
						t = "new TypeFromObject(arg" + h + ")";
					
					targstr += "new TypedObject(" + t + ", arg" + h + ")";
				}
				var fbod = "d.call(\"" + nam + "\", [" + targstr + "], function(ret) { k(ret.getObject()); } );";
				
				argstr += "k";

				//sync toString fix
				if(nam == "toString") {
					fbod = "if(k) { " + fbod + " } else { return \"[object Proxy]\"; }";
				}

				var fstr = "retObj." + nam + " = function(" + argstr + ") { " + fbod + " }";

				eval(fstr);

			}

			return retObj;		
	}
}
*/
function ServicePackage(id, head, socket) {

	var serviceID = id.split("PACK")[0];

	this.getPackageID = function() {
		return id;
	}
	
	this.getServiceID = function() {
		return serviceID;
	}

	this.getHead = function() {
		return head;
	}
	
	this.getTITTSocket = function() {
		return socket;
	}
}
function TITTInternal() {
	
	var PING		= "cumdumpster", 
		INTERFACE	= "showMeYourTITTies";

	this.ping = function() {
		return 42;
	}
	
	this.getInterface = function(o) {
		var s = new Array();

		s[0] = [Types.toNative(o).asString()];
		var h = 1;

		for(var id in o) {
			var t = new Array();
			t[0] = Types.UNKNOWN.asString();
			t[1] = id;
			if(typeof(o[id]) == "function") {
				for(var i = 2; i < 2 + o[id].length; i++) {
					t[i] = Types.UNKNOWN.asString();
				}
				s[h++] = t;
			}
		}

		return s;
	}
	
	this.call = function(o, methodName) {
		
		if(methodName == PING)
			return new TypedObject(Types.INT, this.ping());
		
		if(methodName == INTERFACE)
			return new TypedObject(Types.toArray(Types.toArray(Types.STRING)), this.getInterface(o));

		return null;
	}
	
	
}
function TITTService() {
	var me = this;
	var ef = IOFactory.createEncodingFacade();

	var hostID;
	var registered = [];
	var registeredIDs = [];
	
	function createServiceObject(o, tokens, serviceID) {
		o.__titt__serviceID = serviceID;
		o.__titt__tokens = tokens;

		var sos = new ServiceObjectServer(o, new MethodInvoker(o), new MethodParser(), serviceID);

		registeredIDs[registeredIDs.length] = serviceID;
		registered[serviceID] = sos;
			
		return sos;
	}

	this.register = function(o, tokens, k) {
		
		me.unregister(o, function() {
			if (!hostID) {
				ServerProxy.register(o, tokens, function (serviceID) {
					hostID = serviceID;

					var sos = createServiceObject(o, tokens, serviceID);			
					var internal = new TITTInternal();					
					
					ServerProxy.detectErrors(serviceID, function(){});				

					function handlePackage() {
						ServerProxy.withdraw(serviceID, invokeAndRespond);
					}

					function invokeAndRespond(pack) {
						if(pack) {
							
							var asos = registered[pack.getServiceID()];
							
							var mh = asos.getMethodParser().parse(pack.getHead());
							
							if(mh.getMethodName() == null) {
								ServerProxy.syncResponse(418, pack.getPackageID(), handlePackage);
								delete pack;
								return;
							}
							
							var res = internal.call(asos.getObject(), mh.getMethodName());
							
							if(res != null)
								ServerProxy.syncResponseContent(200, res, pack.getPackageID(), handlePackage);
							else {
	
								var params = new Array(), i = 0;
	
								for(var h = 0; h < mh.getParams().length; h++)
									params[i++] = mh.getParams()[h];
	
								if(mh.getHTTPMethod() == TITTValues.POST) {
									var pr = new POSTReader(pack.getTITTSocket(), ef, new FormHead(mh));
									
									var postParams = pr.readValues();
	
									for(var h = 0; h < postParams.length; h++)
										params[i++] = postParams[h];
								}
								//try catch here
								var stat = 200;
								try {
									res = asos.getMethodInvoker().call(mh.getMethodName(), params);
								}
								catch(err) {
									stat = 418;
									res = new TypedObject(Types.STRING, ""+err);								
								}
								ServerProxy.syncResponseContent(stat, res, pack.getPackageID(), handlePackage);
								
								//tidy
								delete stat; delete params;
							}
							//method tidy
							delete res; delete pack;
						}
					}
					
					handlePackage();
					k();
					
				});
				
			}
			else 
				ServerProxy.registerWithLink(o, tokens, hostID, function(serviceID) {
					createServiceObject(o, tokens, serviceID);
					out("toks " + o.__titt__tokens)
					k();
				});
		
			
		});
		
	} 	
	
	function reregister(c, k) {
		if(c == 0) 
			return k();
		var o = registered[registeredIDs[0]];
		me.register(o.getObject(), o.getObject().__titt__tokens, function() {
			reregister(c-1,k);
		});
	}
	
	function arrRemove(array, from, to) {
  		var rest = array.slice((to || from) + 1 || array.length);
  		array.length = from < 0 ? array.length + from : from;
  		return array.push.apply(array, rest);
	};
	
	this.unregister = function(o, k) {
		if(o.__titt__serviceID) {
			var sid = o.__titt__serviceID;
			o.__titt__serviceID = null;
			o.__titt__tokens = null;
			
			for (var i = 0; i < registeredIDs.length; i++) 
				if (registeredIDs[i] == sid) {
					registered[registeredIDs[i]] = null;
					arrRemove(registeredIDs, i)
					break;
				}
			
			ServerProxy.unregister(sid, function() {
				if(sid == hostID) {
					hostID = null;
					reregister(registeredIDs.length, k);
				}
				else
					k();
			});	
		}
		else {
			k();
		}
		
	}
	
}
function Type(typeStr) {
	
	this.typeStr = typeStr;

	this.isArray = function() {
		return this.typeStr.indexOf(Types.ARRAY_PREFIX) == 0;
	}

	this.isNative = function() {
		return this.typeStr.indexOf(Types.NATIVE_PREFIX) == 0;
	}

	this.subtype = function() {
		if(this.isArray())
			return new Type(this.asString().substring(Types.ARRAY_PREFIX.length,this.asString().length));
		if(this.isNative())
			return new Type(this.asString().substring(Types.NATIVE_PREFIX.length,this.asString().length));
		return new Type(this.asString());
	}

	this.equals = function(t) {
		return t.asString() == this.typeStr;
	}
	
	this.equalsAny = function(ts) {
		for(var i = 0; i < ts.length; i++)
			if(ts[i].equals(this)) return true;
		return false;
	}
	
	this.asString = function() {
		return this.typeStr;
	}
	
	this.toString = this.asString;
}

//type overloaded
function TypeFromObject(obj) {
	
	Type.call(this,getType(obj).asString());
	
	function getType(o) {
		if(o == undefined || o == null) return Types.UNKNOWN;
		
		if(typeof(o) == "number" || o.constructor == Number) {
			if(parseInt(o) == o)
				return Types.INT;
			return Types.DOUBLE;
		}
		if(typeof(o) == "string" || o.constructor == String)  return Types.STRING;
		if(typeof(o) == "boolean" || o.constructor == Boolean) return Types.BOOLEAN;
		if(o.constructor == Array) {
			for(var i = 0; i < o.length; i++) {
				if(getType(o[i]).isNative())
					return Types.toNative(o);
			}
			var sub = Types.UNKNOWN;
			if(o.length > 0)
				sub = getType(o[0]);
			if(!sub.isNative())
				return Types.toArray(sub);
		}
		return Types.toNative(o);
	}
	
}

function TypedObject(t,o) {
	
	this.t = t;
	this.o = o;
	
	this.getObject = function() {
		return this.o;
	}
	
	this.getType = function() {
		return this.t;
	}
}
window.Types = new function() {
	//Prefixes and tokens
	this.ARRAY_PREFIX       = "array_";
	this.NATIVE_PREFIX      = "javascript_";
	this.NULL_TOKEN         = "!0";
	this.EMPTY_STRING_TOKEN = "!1";
	
	//Array enc bytes;
	this.ARRAY_SPLITTER = ",";
	this.ARRAY_START    = "(";
	this.ARRAY_END      = ")";
	
	//Primitive types
	this.BYTE    = new Type("byte");
	this.CHAR    = new Type("char");
	
	this.INT     = new Type("int");
	this.LONG    = new Type("long");
	this.SHORT   = new Type("short");
	this.FLOAT   = new Type("float");
	this.DOUBLE	 = new Type("double");

	this.BOOLEAN = new Type("boolean");
	this.STRING	 = new Type("string");
	
	//Special types
	this.VOID    = new Type("void");
	
	//Dynamic types
	this.UNKNOWN = new Type("unknown");
	
	//Type functions
	this.toArray = function(t) {
		return new Type(this.ARRAY_PREFIX + t.asString());
	}
		
	this.toNative = function(o) {
		if(o == null || o == undefined) return new Type(this.NATIVE_PREFIX + "object");
		
		var a = "" + o.constructor;
		var cl = a.substring("function ".length,a.indexOf("("));
		if(cl == "") cl = "object";
		return new Type(this.NATIVE_PREFIX + cl);
	}	
	
}
window.TITTValues = new function() {

	this.PREFIX_CALLBACK = "callback";
	
	this.GET     = 0,
	this.POST    = 1,
	this.OPTIONS = 2;
	
	
	this.HTTP_NEWLINE = "\r\n";
	this.HTTP_NEWLINE_LENGTH = 2;
		
	this.MULTIPART_SEPERATOR = "--";
	
	this.methodAsString = function(method) {
		if(method == this.GET)
			return "GET";
		if(method == this.POST)
			return "POST";
		return "OPTIONS";
	}
		
	this.getResponse = function(statusCode) {
		var v = "HTTP/1.1 ";
		if(statusCode == 200) 
			return v + "200 OK";
		if(statusCode == 307)
			return v + "307 Temporary Redirect";
		if(statusCode == 400)
			return v + "400 Bad Request";
		if(statusCode == 409)
			return v + "409 Conflict";
		if(statusCode == 404)
			return v + "404 Not Found";
		if(statusCode == 411)
			return v + "411 Length Required";
		if(statusCode == 418)
			return v + "418 I'm a teapot";
		if(statusCode == 423)
			return v + "423 Locked";
		if(statusCode == 503)
			return v + "503 Service Unavailable";
		return v + "200 OK";
	}
}
window.KeyFactory = new function() {
	
	this.SEP = "/";
	this.WILDCARD = "*";
		
	this.createKey = function(tokens) {
		return new TITTKey(tokens);
	}	
		
}
window.IOFactory = new function() {
		
	var sockets = [];
	var ef = null;
		
	/*
	 * TITTSocket
	 */
	
	function findSocket() {
		for(var i = 0; i < sockets.length; i++) {
			if(sockets[i].isClosed)
				return sockets[i]; 
		}
		sockets[sockets.length] = new TITTSocket();
		return sockets[sockets.length-1];
	}
	
	this.createTITTSocket = function() {
		var sock = findSocket();
		sock.isClosed = false;
		return sock;
	}
			
	/*
	 * Encoders / Decoders here
	 */
	
	this.createEncoder = function() {
		return new CompositeEncoder([new ArrayEncoder(new AsStringEncoder()), new ObjectEncoder(), new VoidEncoder()]);
	}
	
	this.createDecoder = function() {
		return new CompositeDecoder([new ArrayDecoder(new AsStringDecoder()), new ObjectDecoder(), new VoidDecoder()]);
	}
	
	
	/*
	 * HeaderWriters / HeaderReaders here
	 */
	
	/*
	 * Facades here
	 */
	 
	this.createEncodingFacade = function() {
		if(ef) return ef;
		
		ef = new function() {
			var e = IOFactory.createEncoder();
			var d = IOFactory.createDecoder();

			this.encode = function(t,o) {
				return e.encode(t,o);
			}
			this.isEncodeable = function(t) {
				return e.isEncodeable(t);
			}
			this.decode = function(t,e) {
				return d.decode(t,e);
			}
			this.isDecodeable = function(t) {
				return d.isDecodeable(t);
			}
		}
		
		return ef;
	}
	
}


window.HeadFactory = new function() {

	this.EMPTY = new function(){
		this.getStatusLine = function(){return null;}
		this.getAllHeaders = function(){return [];}
		this.getHeader = function(nam) {return null;}
	};
	
	this.generateBoundary = function() {
		var ret = "---------------------------";
		for(var i = 0; i < 29; i++)
			ret += ""+Math.floor(Math.random()*10);
		return ret;
	}
	
	this.createHead = function(statusLine, headers) {
		return new Head(statusLine, headers);
	}
	
	this.createHead1 = function(headers) {
		return new Head(null, headers);
	}
	
	this.createHead2 = function(h, headers) {
		var a = new Array();
		var i = 0;
		var all = h.getAllHeaders();
		
		for(var id in headers)
			a[i++] = headers[id];
		for(var id in all)
			a[i++] = all[id];
			
		return this.createHead(h.getStatusLine(), a);
	}

	this.createHead3 = function(h,statusLine,headers) {
		return this.createHead(statusLine, this.createHead2(h, headers).getAllHeaders());
	}	
}
window.ValueConfig = new function() {
	
	this.getLocation = function() {
		var l = ""+self.location;
		l = l.substring("http://".length, l.length); 
		l = l.substring(0, l.indexOf("/"));
		return l;	
	}
	
}
//dep
window.TITT=new function(){var ser,varargs,gen,_cp=1;var _f=_frm(arguments);return _f.clsr(function(){_s:while(1){switch(_cp){case 1:_cp=2;case 2:if((_f.v2=_f._new(
TITTProxyGenerator))===_S){break _s;}_cp=3;case 3:if((_f.v3=_f._new(BlockingProxyGenerator,[_f.v2]))===_S){break _s;}gen=_f.v3;_cp=4;case 4:if((_f.v4=_f._new(
TITTService))===_S){break _s;}ser=_f.v4;
this.createProxy=function(){var _f = _frm(arguments);var _cp=_f.scp;_s:while(1){switch(_cp){case 1:_cp=2;case 2:if((_f.v2=
varargs(this.createProxy.arguments,0))===_S){break _s;}_cp=3;case 3:if((_f.v3=_f._new(TITTInvoker,[_f.v2]))===_S){break _s;}_cp=4;case 4:if((_f.v4=gen.generateProxy(_f.v3))===_S){break _s;}return _f.v4;return;case 0:_cp=_f.cp;}}return _f.sus(_cp,this)};
this.register=function(o){var _f = _frm(arguments);var _cp=_f.scp;_s:while(1){switch(_cp){case 1:_cp=2;case 2:if((_f.v2=
TITTSyncing.sync(ser.register))===_S){break _s;}_cp=3;case 3:if((_f.v3=varargs(this.register.arguments,1))===_S){break _s;}_cp=4;case 4:if((_f.v4=_f.v2(o,_f.v3))===_S){break _s;}return;case 0:o=_f[0];_cp=_f.cp;}}return _f.sus(o,_cp,this)};
this.unregister=function(o){var _f = _frm(arguments);var _cp=_f.scp;_s:while(1){switch(_cp){case 1:_cp=2;case 2:if((_f.v2=
TITTSyncing.sync(ser.unregister))===_S){break _s;}_cp=3;case 3:if((_f.v3=_f.v2(o))===_S){break _s;}return;case 0:o=_f[0];_cp=_f.cp;}}return _f.sus(o,_cp,this)};
varargs = function(args,from){var _f = _frm(arguments);var _cp=_f.scp;_s:while(1){switch(_cp){case 1:_cp=2;case 2:if((_f.v2=_f._new(
Array))===_S){break _s;}var arr=_f.v2;
for(var i=from;i<args.length;i++)
arr[i-from]=args[i];
return arr;return;case 0:arr=_f[0];i=_f[1];args=_f[2];from=_f[3];_cp=_f.cp;}}return _f.sus(arr,i,args,from,_cp,this)};return;}}return _f.sus(this)}).call(this)};


/*
window.TITT = new function() {

	var gen = new BlockingProxyGenerator(new TITTProxyGenerator())
	var ser = new TITTService();
	
	this.createProxy = function() {
		return gen.generateProxy(new TITTInvoker(varargs(this.createProxy.arguments, 0)));	
	}
	
	this.register = function(o) {
		TITTSyncing.sync(ser.register)(o, varargs(this.register.arguments, 1));
	}
	
	this.unregister = function(o) {
		TITTSyncing.sync(ser.unregister)(o);
	}
		
	function varargs(args, from) {
		var arr = new Array();
		for(var i = from; i < args.length; i++)
			arr[i-from] = args[i];
		return arr;
	}

}
*/
//dep
window.ServerProxy = new function() {
	
	var ef = IOFactory.createEncodingFacade();
	var inc = 0;
	var err = 0;
		
	this.listServices = function(k) {
		var inv = new TITTInvoker(["titt"]);
		inv.dynCall("list", [], k);
	}
	
	this.register = function(o, tokens, k) {
		this.registerWithLink(o, tokens, null, k);
	}
	
	this.registerWithLink = function(o, tokens, lnk, k) {
		var arr = new Array();
		arr[0] = "titt";
		for(var i = 0; i < tokens.length; i++)
			arr[i+1] = tokens[i];
		
		var inv = new TITTInvoker(arr);
		var internal = new TITTInternal();
		
		var regInterface = new TypedObject(Types.toArray(Types.toArray(Types.STRING)), internal.getInterface(o));
		
		var args = [regInterface];
		if(lnk)
			args = [regInterface, new TypedObject(Types.STRING, lnk)];

		inv.call("register", args, function(ret){ k(ret.getObject()); });
	}
	
	this.detectErrors = function(serviceID, k) {
		var ts = IOFactory.createTITTSocket();
		
		ts.getHeadWriter().writeHead(createTITTCallHead(TITTValues.GET, "error", [serviceID]));
		ts.getOutputStream().flush(function(){
			k();
			ts.close();
			delete ts;
		});
	}
	
	
	this.syncResponseContent = function(stat, ret, packageID, k) {
		var socket = this.sync(packageID)

		function innerk(){
			k();
			socket.close();
			delete socket;
		}
		
		if(ret) {
			var rw = new ResponseWriter(socket, ef, new ResponseHeadInit(stat, HeadFactory.EMPTY));
			rw.writeValues([ret], innerk);
		}	
		else {
			socket.getHeadWriter().writeHead(new ResponseHeadInit(stat, HeadFactory.EMPTY));
			socket.getOutputStream().flush(innerk);
		}
	}
	
	this.syncResponse = function(stat, packageID, k) {
		this.syncResponseContent(stat, null, packageID, k);
	}
	
	this.sync = function(packageID) {
		var socket = IOFactory.createTITTSocket();
		socket.getHeadWriter().writeHead(createTITTCallHead(TITTValues.POST, "sync", [packageID]));
		return socket;
	}
	
	this.unregister = function(serviceID, k) {
		var socket = IOFactory.createTITTSocket();
		socket.getHeadWriter().writeHead(createTITTCallHead(TITTValues.GET, "unregister", [serviceID]));
		socket.getOutputStream().flush(function() {
			socket.getHeadReader().readHead();
			k();
			socket.close();
			delete socket;
		});
	}
	
	this.withdraw = function(serviceID, k) {
		var sp = this;
		var socket = IOFactory.createTITTSocket();
		
		socket.getHeadWriter().writeHead(createTITTCallHead(TITTValues.GET, "withdraw", [serviceID, inc++]));
		socket.getOutputStream().flush(function() {
			try {
				var resp = new ResponseHead(socket.getHeadReader().readHead());
				err = 0;
				if (resp.getStatusCode() != 200) {					
					k(null);
				}
				else {
					var pack = new ServicePackage(resp.getHeader("E-Tag").getValue(), socket.getHeadReader().readHead(), socket);
					k(pack);
				}
			}
			catch(errmsg) {
				if(err > 1) {
					k(null);
					return;
				}
				err++;
				//try again...
				sp.withdraw(serviceID, k);
			}
			socket.close();
			delete sp; delete socket; 
		});
		
	}
	
	function createTITTCallHead(method, methodName, args) {
		var tos = new Array();
		for(var i = 0; i < args.length; i++)
			tos[i] = new TypedObject(new TypeFromObject(args[i]), args[i]);
	
		var req = new RequestHeadInit(method, "/titt/", HeadFactory.EMPTY);
		return new MethodHeadInit(methodName, tos, ef, req);
	}
}
//dep
window.TITTNuder=new function(){var gen,inc,enc,varargs,_cp=1;var _f=_frm(arguments);return _f.clsr(function(){_s:while(1){switch(_cp){case 1:_cp=2;case 2:if((_f.v2=

IOFactory.createEncodingFacade())===_S){break _s;}enc=_f.v2;_cp=3;case 3:if((_f.v3=_f._new(
TITTProxyGenerator))===_S){break _s;}_cp=4;case 4:if((_f.v4=_f._new(BlockingProxyGenerator,[_f.v3]))===_S){break _s;}gen=_f.v4;
inc=0;

this.encode=function(str){var _f = _frm(arguments);var _cp=_f.scp;_s:while(1){switch(_cp){case 1:_cp=2;case 2:if((_f.v2=
enc.encode(Types.STRING,str))===_S){break _s;}return _f.v2;return;case 0:str=_f[0];_cp=_f.cp;}}return _f.sus(str,_cp,this)};


this.createProxy=function(){var _f = _frm(arguments);var _cp=_f.scp;_s:while(1){switch(_cp){case 1:_cp=2;case 2:if((_f.v2=
varargs(this.createProxy.arguments,0))===_S){break _s;}_cp=3;case 3:if((_f.v3=_f._new(NuderInvoker,[_f.v2]))===_S){break _s;}_cp=4;case 4:if((_f.v4=gen.generateProxy(_f.v3))===_S){break _s;}return _f.v4;return;case 0:_cp=_f.cp;}}return _f.sus(_cp,this)};


this.generateID=function(seed){
return "___nuder___"+seed+"___"+inc++;};


varargs = function(args,from){var _f = _frm(arguments);var _cp=_f.scp;_s:while(1){switch(_cp){case 1:_cp=2;case 2:if((_f.v2=_f._new(
Array))===_S){break _s;}var arr=_f.v2;
for(var i=from;i<args.length;i++)
arr[i-from]=args[i];
return arr;return;case 0:arr=_f[0];i=_f[1];args=_f[2];from=_f[3];_cp=_f.cp;}}return _f.sus(arr,i,args,from,_cp,this)};return;}}return _f.sus(this)}).call(this)};




/*
window.TITTNuder = new function() {
	
	var enc = IOFactory.createEncodingFacade();
	var gen = new BlockingProxyGenerator(new TITTProxyGenerator())
	var inc = 0;
	
	this.encode = function(str) {
		return enc.encode(Types.STRING, str);
	}
	
	this.createProxy = function() {
		return gen.generateProxy(new NuderInvoker(varargs(this.createProxy.arguments, 0)));	
	}
	
	this.generateID = function(seed) {
		return "___nuder___" + seed + "___" + inc++;
	}

	function varargs(args, from) {
		var arr = new Array();
		for(var i = from; i < args.length; i++)
			arr[i-from] = args[i];
		return arr;
	}
	
}
*/
